package org.dei.perla.sys.device.adapter;

import java.util.ArrayList;

import org.dei.perla.component.Component;
import org.dei.perla.sys.device.channel.ChannelManager;
import org.dei.perla.utils.logger.LogRecord;
import org.dei.perla.utils.messages.Message;
import org.dei.perla.utils.messages.MsgUtility;
import org.dei.perla.utils.pipe.Pipe;
import org.dei.perla.utils.waiter.Waitable;
import org.dei.perla.utils.waiter.Waiter;

/**
 * Classe astratta che elenca i principali metodi di un adapter server
 * o un gateway
 * 
 */

public abstract class AdapterServer extends Component{
	
	/**
	 * Contatore progressivo dei Vci o Bvci
	 */
	protected int pChannelIdCounter;
	
	/**
	 * Event listener
	 */
	protected Thread pEventListener;
	
	/**
	 * Gestore di tutte le pipe di input dell'Adapter Server
	 */
	protected Waiter pWaiter;
	
    /**
     * Canali collegati
     */
	protected ArrayList<ChannelManager> pChannelsMng;
	
	/**
	 * Tabella di routing
	 */
	protected AdapterSrvRoutingTable pReg;
	
	/**
	 * Costruttore
	 * 
	 * @param parName, nome del componente
	 * @param parChannels, channel managers collegati
	 */
	public AdapterServer(String parName, ArrayList<ChannelManager> parChannels ){
		
		super(parName);
		
		this.pChannelsMng = parChannels;
		
		this.pReg = new AdapterSrvRoutingTable(this);
		
		// Inizializzo il gestore delle pipe
		this.pWaiter = new Waiter();
		
		for(int i = 0; i < this.pChannelsMng.size(); i++){
			
			this.pWaiter.addWaitable(this.pChannelsMng.get(i).getInputPipe().getWaitable());
			
		}
		
		this.initialize();
		
	}
	
	/**
	 * Inizializza tutti i componenti
	 */
	abstract protected void initialize();
	
	/**
	 * Controlla tutte le pipe e risveglia i metodi relativi alle pipe
	 * su cui sono arrivati i messaggi
	 */
	public void start(){
		
		if(this.isStarted() != true){
			
			this.setStarted(true);
			
			this.pWaiter.start();
			
		    //Avvia il thread di ricezione
			this.pEventListener = new Thread() {
				public void run() {
					pReceive();
				}
			};
			this.pEventListener.start();
			
		}
		
	}
	
	/**
	 * Si mette in ricezione sulle pipe
	 *
	 */
	@SuppressWarnings("unchecked")
	public void pReceive(){
		
		Waitable tWaitable = null;
		
		do{
			
			tWaitable = this.pWaiter.waitNext();
			
			if (tWaitable != null){
				
				// Ottieni la pipe che ha scatenato l'evento
				Pipe pipe = (Pipe) tWaitable.getParentComponent();
				
				this.receiveManager(pipe);
				
			}
			
		} while(tWaitable != null);
		
	}
	
	/**
	 * Ferma l'esecuzione del componente
	 */
	public void stop(){
		
		this.setStarted(false);
		
		this.pWaiter.stop();
		try {
			this.pEventListener.join();
		}
		catch(Exception parException) { }
		this.pEventListener = null;
		
		
	}
	

	/**
	 * Decide cosa fare in base alla pipe che riceve un messaggio.
	 * Viene chiamato in seguito ad un evento scatenato da una pipe.
	 * 
	 * @param parPipe, la pipe che ha ricevuto un messaggio
	 */
	public void receiveManager(Pipe<Message> parPipe){
		
		Message pMsg = (Message) parPipe.dequeue();
		byte[] tPayload = pMsg.getPayload();
		if(this.pIsBindingMessage(tPayload)){
			
			this.loggerNotify(LogRecord.Type.Information, "Binding message received", LogRecord.Verbosity.Medium);
			mngBindingMsg(pMsg, parPipe);
		}
		else if(this.pIsAckMessage(tPayload)){
			
			this.loggerNotify(LogRecord.Type.Information, "Binding ACK message received", LogRecord.Verbosity.Medium);
			mngAckMessage(pMsg);
		}
		else if(this.pIsNegativeAckMessage(tPayload)){
			
			this.loggerNotify(LogRecord.Type.Information, "Negative binding ACK message received", LogRecord.Verbosity.Medium);
			mngNegativeAckMsg(pMsg);
		}
		else{
			
			this.loggerNotify(LogRecord.Type.Information, "Normal message received", LogRecord.Verbosity.Medium);
			mngNormalMsg(pMsg, parPipe);
		}
		
	}
	
	/**
	 * Gestisce un messaggio di binding
	 * @param parMsg messaggio da gestire
	 * @param parPipe pipe sulla quale è arrivato il messaggio
	 */
	abstract protected void mngBindingMsg(Message parMsg, Pipe<Message> parPipe);
	
	/**
	 * Gestisce un messaggio di ack
	 * @param parMsg messaggio da gestire
	 */
	abstract protected void mngAckMessage(Message parMsg);
	
	/**
	 * Gestisce un messaggio di negative ack
	 * @param parMsg messaggio da gestire
	 */
	abstract protected void mngNegativeAckMsg(Message parMsg);
	
	/**
	 * Gestisce un messaggio normale
	 * @param parMsg messaggio da gestire
	 */
	abstract protected void mngNormalMsg(Message parMsg, Pipe<Message> parPipe);
	
	/**
	 * Controlla il flag del messaggio per stabire se
	 * è un messaggio di binding
	 * 
	 * @param parPayload, payload da controllare
	 * @return true se è un messaggio di binding
	 */
	protected boolean pIsBindingMessage(byte[] parPayload){
		
		if(MsgUtility.getFlag(parPayload) == MsgUtility.flagBinding)
			return true;
		
		return false;
		
	}
	
	/**
	 * Controlla il flag del messaggio per stabire se
	 * è un messaggio di ack
	 * 
	 * @param parPayload, payload da controllare
	 * @return true se è un messaggio di ack
	 */
	protected boolean pIsAckMessage(byte[] parPayload){
		
		if(MsgUtility.getFlag(parPayload) == MsgUtility.flagAck)
			return true;
		
		return false;
		
	}
	
	/**
	 * Controlla il flag del messaggio per stabire se
	 * è un messaggio di negativeAck
	 * 
	 * @param parPayload, payload da controllare
	 * @return true se è un messaggio di negativeAck
	 */
	protected boolean pIsNegativeAckMessage(byte[] parPayload){
		
		if(MsgUtility.getFlag(parPayload) == MsgUtility.flagNegativeAck)
			return true;
		
		return false;
		
	}
	
	/**
	 * Crea un univoco id per il canale
	 * 
	 * @return id univoco
	 */
	protected int pCreateUniqueChannelId(){
		
		return this.pChannelIdCounter++;
		
	}


}